package alfabankapi

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
)

type ApiClient struct {
	URL              string
	ClientID         string
	RedirectURI      string
	Scope            string
	State            string
	tlsConfig        *tls.Config
	clientSecretFile string // file to store the client secret
	clientSecret     string // cached client secret
	token            *Token
	tokenFile        string //файл с токеном
}

type APIConfig struct {
	Sandbox          bool   //включаем песочницу или нет
	ClientID         string //идентифиĸатор приложения Партнера API, полученный после регистрации приложения на стороне Банĸа
	RedirectURI      string //URI-адрес перенаправления для получения ĸода авторизации (можно использовать http://localhost
	Scope            string //области применения тоĸена доступа, уĸазывают ĸаĸие права должен содержать тоĸен доступа (можно уĸазать несĸольĸо через пробел), должны быть озвучены при регистрации приложения на стороне Банĸа. Например, для получения выписĸи необходимо уĸазать openid transactions
	State            string //с любым строĸовое значением - string12345
	CertFile         string //сертификат для клиента (открытый ключ)
	KeyFile          string //закрытый ключ
	Password         string //пароль закрытого ключа
	CAFile           string //файл с CA сертификатами (расширение .pem) Минцифр
	TokenFile        string //файл с токеном
	ClientSecretFile string // file to store the client secret
}

func NewClient(conf *APIConfig) (client *ApiClient) {
	client = new(ApiClient)

	switch conf.Sandbox {
	case true:
		client.URL = "https://sandbox.alfabank.ru"
	case false:
		client.URL = "https://baas.alfabank.ru"
	}
	client.ClientID = conf.ClientID
	client.RedirectURI = conf.RedirectURI
	client.Scope = conf.Scope
	client.State = conf.State

	tlsConfig, err := loadTLSConfig(conf.CertFile, conf.KeyFile, conf.Password, conf.CAFile, conf.Sandbox)
	if err != nil {
		log.Fatal(errors.New("failed to load TLS config: " + err.Error()))
	}
	client.tlsConfig = tlsConfig

	client.tokenFile = conf.TokenFile

	client.clientSecretFile = conf.ClientSecretFile
	clientSecret, err := client.GetClientSecret()
	if err != nil {
		log.Fatal(errors.New("failed to get client secret: " + err.Error()))
	}
	client.clientSecret = clientSecret

	return
}

func (c *ApiClient) makeRequest(method, url string, headers map[string]string, body []byte) ([]byte, error) {
	tr := &http.Transport{TLSClientConfig: c.tlsConfig}
	client := &http.Client{Transport: tr}
	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, errors.New("failed to create request: " + err.Error())
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		var errorResponse struct {
			Error     string `json:"error"`
			ErrorDesc string `json:"error_description"`
		}
		responseBody, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = json.Unmarshal(responseBody, &errorResponse)
		if err != nil {
			return nil, err
		}

		switch resp.StatusCode {
		case http.StatusBadRequest:
			return nil, fmt.Errorf("bad request: %s", errorResponse.ErrorDesc)
		case http.StatusUnauthorized:
			return nil, fmt.Errorf("unauthorized: %s", errorResponse.ErrorDesc)
		case http.StatusNotFound:
			return nil, fmt.Errorf("not found: %s", errorResponse.ErrorDesc)
		case http.StatusInternalServerError:
			return nil, fmt.Errorf("internal server error: %s", errorResponse.ErrorDesc)
		default:
			return nil, fmt.Errorf("unknown error: %s", errorResponse.ErrorDesc)
		}
	}

	return io.ReadAll(resp.Body)
}

// https://developers.alfabank.ru/products/alfa-api/documentation/articles/transactions/articles/statement/v1/statement?navFilter=h2h
func (c *ApiClient) GetStatementTransactions(accountNumber string, statementDate string) ([]Transaction, error) {
	url := fmt.Sprintf("%s/api/statement/transactions?accountNumber=%s&statementDate=%s",
		c.URL, accountNumber, statementDate)
	headers := map[string]string{
		"Accept":        "application/json",
		"Authorization": fmt.Sprintf("Bearer %s", c.token.AccessToken),
		"Content-Type":  "application/json",
	}
	body := []byte{}
	resp, err := c.makeRequest("GET", url, headers, body)
	if err != nil {
		return []Transaction{}, fmt.Errorf("failed to make request: %w", err)
	}

	type Response struct {
		Links        []Link        `json:"_links"`
		Transactions []Transaction `json:"transactions"`
	}

	var response Response
	err = json.Unmarshal(resp, &response)
	if err != nil {
		return []Transaction{}, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return response.Transactions, nil
}
